#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>

#include <openssl/rsa.h>
#include <openssl/pem.h>

#include <event2/event.h>
#include <event2/util.h>
#include <event2/listener.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "cmd.h"
#include "utils.h"

#define DEFAULT_SERIAL_PORT "/dev/ttyUSB0"
#define ETHERNET_TRANSPORT

/* global context */
struct auth_context {
	struct event_base *base;
	struct bufferevent *serial_bev;
	struct bufferevent *stdbev;
	int serial_fd;
	struct event *sched_timeout;
	int pending;

	uint8_t buf[1024];
};

struct request {
	struct request *next;
	uint32_t user_id;
	uint8_t  imei[IMEI_LEN];
	uint8_t  baddr[BT_ADDR_LEN];
	uint8_t  waddr[WIFI_ADDR_LEN];
};

static struct request *request_list;

void add_to_request_list(struct request *req)
{
	if (!request_list) {
		request_list = req;
		req->next = NULL;
	} else {
		req->next = request_list;
		request_list = req;
	}
}

void delete_from_request_list(struct request *req)
{
	struct request **p;

	for (p = &request_list; *p; p = &(*p)->next)
		if (*p == req) {
			*p = req->next;
			break;
		}
}

static void handle_serial_rsp_boxid(struct bufferevent *bev, void *data, uint32_t len, void *ctx)
{
	struct auth_context *context = ctx;
	struct resp_box_id *rsp = data;
	struct cmd_hdr *hdr = (struct cmd_hdr *)context->buf;

	dbg("in func: %s", __func__);
	dbg("boxid: %08X, commid: %08X, ukey: %08X", rsp->dwBoxID, rsp->dwCommID, rsp->dwUkeyID);
	dbg("magic: %s", rsp->magic);

	// send auth q request
	hdr->bType = CMD_REQ_BOX_AUTH_QUE;
	hdr->wLength = 0;

	bufferevent_write(bev, hdr, CMD_HDR_LEN);
}

static void handle_serial_rsp_auth_q(struct bufferevent *bev, void *data, uint32_t len, void *ctx)
{
	//struct auth_context *context = ctx;
	struct resp_box_auth_queue *rsp = data;
	int count = rsp->bQSize;
	struct resp_box_auth_item *item = rsp->items;
	struct request *r;
	int i;

	dbg("in func: %s", __func__);
	for (i = 0; i < count; i++) {
		//dbg("user_id: %08X, btaddr: %08X, ", 
		//item->dwUserId, item->strBTAddr, item->strIMEI, item->strMACAddr);
		dump(__func__, item, sizeof(*item));
		printf("accept ?> ");
		fflush(stdout);
		//evbuffer_write(context->std);

		r = calloc(1, sizeof(*r));
		r->user_id = item->dwUserId;
		memcpy(r->imei, item->strIMEI, IMEI_LEN);
		memcpy(r->baddr, item->strBTAddr, BT_ADDR_LEN);
		memcpy(r->waddr, item->strMACAddr, WIFI_ADDR_LEN);

		add_to_request_list(r);
	}
}

static void handle_serial_rsp_auth_conf(struct bufferevent *bev, void *data, uint32_t len, void *ctx)
{
}

int process_serial(struct bufferevent *bev, struct cmd_hdr *hdr, void *ctx)
{
	int type;
	uint32_t len;

	type = hdr->bType;
	len = hdr->wLength;

	switch (type) {
	case CMD_RSP_BOX_ID:
		handle_serial_rsp_boxid(bev, hdr->data, len, ctx);
		break;
	case CMD_RSP_BOX_AUTH_QUE:
		handle_serial_rsp_auth_q(bev, hdr->data, len, ctx);
		break;
	case CMD_RSP_BOX_AUTH_CONFIRM:
		handle_serial_rsp_auth_conf(bev, hdr->data, len, ctx);
		break;
	}

	return 0;
}

static void serial_read_cb(struct bufferevent *bev, void *ctx)
{
	size_t datalen;
	struct cmd_hdr *hdr;
	//int len;

	struct evbuffer *input = bufferevent_get_input(bev);
	//len = evbuffer_get_length(input);

	err("%s %d", __func__, __LINE__);
	/* make sure hdr + data are in linear memory space. */
	hdr = (struct cmd_hdr *)evbuffer_pullup(input, CMD_HDR_LEN);
	if (!hdr) {
		//err("not enough data in the buffer < hdr len");
		return;
	}

	//dump("serial cmd hdr ", hdr, CMD_HDR_LEN);
	datalen = hdr->wLength;
	if (datalen) {
		hdr = (struct cmd_hdr *)evbuffer_pullup(input, CMD_HDR_LEN + datalen);
		if (!hdr){
			//err("not enough data in the buffer < datalen");
			return;
		}
	}

	dump(__func__, hdr, CMD_HDR_LEN + datalen);
	process_serial(bev, hdr, ctx);
	evbuffer_drain(input, CMD_HDR_LEN + datalen);
}

static void serial_event_cb(struct bufferevent *bev, short events, void *ctx)
{
	//struct client *cl = ctx;

	if (events & BEV_EVENT_ERROR)
		err("Error from bufferevent");

	if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
		err("serial port connection closed");

		//request_done(req);
		//client_close(cl);
	}
}


int setup_serial(struct auth_context *context)
{
	int fd;
	struct termios options;

	fd = open(DEFAULT_SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);
	if (fd < 0) {
		perror("Unable to open " DEFAULT_SERIAL_PORT);
		return 1;
	}

	evutil_make_socket_nonblocking(fd);

	// get the current options for the port
	tcgetattr(fd, &options);
	cfmakeraw(&options);
	cfsetispeed(&options, B115200);
	cfsetospeed(&options, B115200);

	if (tcsetattr(fd, TCSAFLUSH, &options) < 0)
		err("failed to configure serial port");

	context->serial_bev = bufferevent_socket_new(context->base, fd, BEV_OPT_CLOSE_ON_FREE);
	bufferevent_setcb(context->serial_bev, serial_read_cb, NULL, serial_event_cb, context);
	bufferevent_enable(context->serial_bev, EV_READ | EV_WRITE);

	context->serial_fd = fd;

	return 0;
}

int setup_eth_transport(struct auth_context * context)
{
	struct sockaddr_in sin;
	//int ret;

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	//sin.sin_addr.s_addr = htonl(0);
	inet_aton("127.0.0.1", &sin.sin_addr);
	//inet_aton("192.168.8.1", &sin.sin_addr);
	sin.sin_port = htons(8890);

	context->serial_bev = bufferevent_socket_new(context->base, -1, BEV_OPT_CLOSE_ON_FREE);
	bufferevent_setcb(context->serial_bev, serial_read_cb, NULL, serial_event_cb, context);
	bufferevent_enable(context->serial_bev, EV_READ | EV_WRITE);

	if (bufferevent_socket_connect(context->serial_bev, 
			(struct sockaddr *)&sin, sizeof(sin)) < 0) {
		bufferevent_free(context->serial_bev);

		return 1;
	}

	return 0;
}

/* issue a poll request */
void sched_poll(evutil_socket_t fd, short what, void *arg)
{
	struct auth_context *context = arg;
	struct cmd_hdr *hdr = (struct cmd_hdr *)context->buf;

	err("%s %d", __func__, __LINE__);

	if (context->pending)
		return;

	context->pending = 1;
	hdr->bType = CMD_REQ_BOX_ID;
	hdr->wLength = 0;

	bufferevent_write(context->serial_bev, hdr, CMD_HDR_LEN);
}

void std_read_cb(struct bufferevent *bev, void *ctx)
{
	struct auth_context *context = ctx;
	struct evbuffer *std_input = bufferevent_get_input(bev);
	//int len = evbuffer_get_length(std_input);
	struct cmd_hdr *hdr = (struct cmd_hdr *)context->buf;
	struct req_box_auth_confirm *req = (struct req_box_auth_confirm *)hdr->data;
	uint8_t *conf_data;

	char buf[64];

	evbuffer_remove(std_input, buf, 64);
	//dbg("%s stdin is %s", __func__, buf);
	if (buf[0] == 'y') {

		struct request *r = request_list;

		dbg("%s send confirmation", __func__);
		for (; r; r = r->next) {
			hdr->bType = CMD_REQ_BOX_AUTH_CONFIRM;

			req->dwUserID = r->user_id;
			req->dwCommID = COMMID;
			req->bDoorAuthNum = 1;
			req->bDoorDeauthNum = 0;
			conf_data = req->data;

			/*
			 * data includes: 
			 * doorid[0:bDoorAuthNum-1]
			 * doorid[0:bDoorDeauthNum-1]
			 * strUserKeyPub
			 */
			{
				BIO *b;
				int pubkey_len;
				*((uint32_t *)conf_data) = DOORID;
				conf_data += 4;

				b = BIO_new_file("kusrpub.key", "rb");
				pubkey_len = BIO_read(b, conf_data, 1024);
				if (!b) {
					err("open kusrpub.key failed");
				}
				err("kusrpubkey len %d", pubkey_len);
				BIO_free(b);

				hdr->wLength = sizeof(*req) + 4 + pubkey_len;
			}

			bufferevent_write(context->serial_bev, hdr, CMD_HDR_LEN + hdr->wLength);
		}
	} else {
		
	}

	context->pending = 0;
}

int main(int argc, char *argv[])
{
	//int val;
	struct auth_context *context = calloc(1, sizeof(*context));
	struct timeval one_sec = {3, 0};

	if (!(context->base = event_base_new())) {
		err("Couldn't open event base");
		return 1;
	}

#ifdef SERIAL_TRANSPORT
	if (setup_serial(context)) {
		err("failed");
		return 1;
	}
#endif

#ifdef ETHERNET_TRANSPORT
	if (setup_eth_transport(context)) {
		err("failed");
		return 1;
	}
#endif

	// monitor stdin
	context->stdbev = bufferevent_socket_new(context->base, STDIN_FILENO, BEV_OPT_CLOSE_ON_FREE);
	bufferevent_setcb(context->stdbev, std_read_cb, NULL, NULL, context);
	bufferevent_enable(context->stdbev, EV_READ | EV_WRITE);
	

	context->sched_timeout = event_new(context->base, -1, EV_PERSIST, sched_poll, context);
	event_add(context->sched_timeout, &one_sec);

	// start loop
	event_base_dispatch(context->base);

	return 0;
}
